package com.mxchip.livestarmobile.mobile.av.player;

import android.graphics.SurfaceTexture;
import android.media.MediaCodec;
import android.media.MediaFormat;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.Surface;

import com.mxchip.hxb3288a.av.player.DecoderOnSurfaceCreatedHandler;
import com.mxchip.livestarmobile.mobile.util.Log.MyLog;

import org.jetbrains.annotations.NotNull;

import java.nio.ByteBuffer;
import java.util.concurrent.ArrayBlockingQueue;

import static android.media.MediaFormat.KEY_HEIGHT;
import static android.media.MediaFormat.KEY_MIME;
import static android.media.MediaFormat.KEY_WIDTH;


public class DecoderHandler implements DecoderOnSurfaceCreatedHandler {

    private static final String TAG = "DecoderHandler";

    private String name;

    private boolean mediaCodecStart = false;

    private static final int QUEUE_SIZE = 8;

    // private MediaCodec.BufferInfo info;
    private MediaCodec mediaCodec;
    private MediaFormat mediaFormat;

    private SurfaceTexture mSurfaceTexture;
    // private Surface mSurface;
    private ArrayBlockingQueue<UserVideoData> inputQueue = new ArrayBlockingQueue<>(QUEUE_SIZE);

    private HandlerThread mediaCodecHandlerThread;
    private MyHandler myHandler;

    public DecoderHandler() {
        mediaCodecHandlerThread = new HandlerThread("SubRendererDrawer_Encode_Thread");
        mediaCodecHandlerThread.start();

        Looper looper = mediaCodecHandlerThread.getLooper();
        myHandler = new MyHandler(looper);
    }

    public void initDecoder(String mimeType, int width, int height) {

        if (this.mediaFormat != null && this.mediaCodec != null
                && this.mediaFormat.getString(KEY_MIME).equals(mimeType)
                && this.mediaFormat.getInteger(KEY_WIDTH) == width
                && this.mediaFormat.getInteger(KEY_HEIGHT) == height) {
            return;
        }

        if (this.mediaCodec != null) {
            try {
                this.mediaCodec.stop();
                this.mediaCodecStart = false;
                Log.d(TAG, "initDecoder: mediaCodecStart changed:" + mediaCodecStart + " " + getName());
                this.mediaCodec.setCallback(null);
                this.mediaCodec.release();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                this.mediaCodecStart = false;
                Log.d(TAG, "initDecoder finally: mediaCodecStart changed:" + mediaCodecStart + " " + getName());
                mediaCodec = null;
            }
        }

        try {
            this.mediaFormat = MediaFormat.createVideoFormat(mimeType, width, height);
            this.mediaCodec = MediaCodec.createDecoderByType(mimeType);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public boolean isMediaCodecStart() {
        return mediaCodecStart;
    }

    public void setName(String name) {
        this.name = name;
        MyLog.logD(MyLog.DECODER_HANDLER_MODEL, "setName " + this);
    }

    public String getName() {
        return name;
    }

    public void resetDecoder() {
        if (mediaCodec != null) {
            mediaCodec.reset();
        }
    }

    /*public void configureDecoder() {
        MediaFormat mediaFormat = MediaFormat.createVideoFormat(mimeType, width, height);
        if(mediaCodec != null) {
            this.mediaCodec.configure(mediaFormat, new Surface(mSurfaceTexture), null, 0);
        }
    }*/

    public void startDecoder() {

        if (isMediaCodecStart()) {
            Log.d(TAG, "startDecoder isMediaCodecStart is true .stopDecoder:  " + getName());
            stopDecoder();
        }

        try {
            this.mediaCodec.setCallback(new MediaCodecCallback());
            this.mediaCodec.configure(mediaFormat, new Surface(mSurfaceTexture), null, 0);
            this.mediaCodec.start();
        } catch (Exception e) {
            e.printStackTrace();
            MyLog.logE(MyLog.DECODER_HANDLER_MODEL, "startDecoder e:" + e.getMessage() + " " + getName());
        }
        this.mediaCodecStart = true;
        Log.d(TAG, "startDecoder: mediaCodecStart changed:" + mediaCodecStart + " " + getName());
//        if (!myThread.isAlive()) {
//            myThread.start();
//        }
        MyLog.logD(MyLog.DECODER_HANDLER_MODEL, "startDecoder " + this);
    }

    public void stopDecoder() {
        if (mediaCodec != null) {
            try {
                this.mediaCodec.stop();
                this.mediaCodec.setCallback(null);
                mediaCodec.reset();
            } catch (Exception e) {
                MyLog.logE(MyLog.DECODER_HANDLER_MODEL, e.toString());
                e.printStackTrace();
            }
        }

        this.mediaCodecStart = false;
        Log.d(TAG, "stopDecoder: mediaCodecStart changed:" + mediaCodecStart + " " + getName());
        MyLog.logD(MyLog.DECODER_HANDLER_MODEL, "stopDecoder " + this);
    }

    public void releaseDecoder() {
        Log.d(TAG, "releaseDecoder .stopDecoder:  " + getName());
        this.stopDecoder();

        try {
            if (mediaCodec != null) {
                this.mediaCodec.release();
            }
        } catch (Exception e) {
            MyLog.logE(MyLog.DECODER_HANDLER_MODEL, "releaseDecoder:e: " + e);
        } finally {
            mediaCodec = null;
        }

        if (myHandler != null) {
            myHandler.removeCallbacksAndMessages(null);
            myHandler = null;
        }

        if (mediaCodecHandlerThread != null) {
            mediaCodecHandlerThread.quitSafely();
            mediaCodecHandlerThread = null;
        }

        try {
            if (inputQueue.isEmpty())
                inputQueue.put(new UserVideoData("", new byte[1024]));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        while (!inputQueue.isEmpty()) {
            inputQueue.remove();
        }
    }

    public void putData(byte[] data, String decoderName) {
        // Log.e(TAG, name + " " + this + " putData inputQueue.size() >= QUEUE_SIZE: " + (inputQueue.size() >= QUEUE_SIZE));

        if (inputQueue.size() >= QUEUE_SIZE) {
            inputQueue.poll();
        }
        try {
            MyLog.logD(MyLog.DECODER_HANDLER_MODEL, "inputQueue.put(new UserVideoData(decoderName, data)); ");
            inputQueue.put(new UserVideoData(decoderName, data));
        } catch (InterruptedException e) {
            MyLog.logE(MyLog.DECODER_HANDLER_MODEL, "decode error: " + e.getMessage());
        }
    }

    // public void setSurface(Surface surface) {
//        this.mSurface = surface;
//    }

    public void setSurfaceTexture(SurfaceTexture surfaceTexture) {
        this.mSurfaceTexture = surfaceTexture;
    }

    //    private Boolean show = false;
    int count = 0;

    private class MediaCodecCallback extends MediaCodec.Callback {
        byte[] data = null;

        @Override
        public void onInputBufferAvailable(@NotNull MediaCodec codec, int index) {
            UserVideoData userVideoData = null;
            userVideoData = inputQueue.poll();
            MyLog.logD(MyLog.DECODER_HANDLER_MODEL, "inputQueue.poll() " + getName());
            count = (count + 1) % 20;
            if (count == 0) {
                MyLog.logD(MyLog.DECODER_HANDLER_MODEL, "onInputBufferAvailable: " + userVideoData);
            }


            if (userVideoData != null) {
                data = userVideoData.videoData;
//                if (show) {
//
//                    MyLog.logD("MediaCodecCallback::onInputBufferAvailable:userVideoData.uidAndDevices:"
//                            + userVideoData.uidAndDevices + " userVideoData.videoData:" + Arrays.toString(userVideoData.videoData));
//                }

            }

            if (name != null) {
                // Log.e(TAG, name + " onInputBufferAvailable data: " + Arrays.toString(data));
            }

            if (data != null && data.length > 0) {

                ByteBuffer inputBuffer = null;
                try {
                    inputBuffer = codec.getInputBuffer(index);
//                    if (show && userVideoData != null) {
//                        MyLog.logD("MediaCodecCallback::onInputBufferAvailable:codec.getInputBuffer:userVideoData.uidAndDevices:"
//                                + userVideoData.uidAndDevices + " index:" + index + " inputBuffer:" + inputBuffer);
//                    }
                    if (inputBuffer == null) {
                        MyLog.logE(MyLog.DECODER_HANDLER_MODEL, "handleMessage: inputBuffer == null:");
                    }
                } catch (Exception e) {
                    MyLog.logE(MyLog.DECODER_HANDLER_MODEL, "handleMessage: codec.getInputBuffer:" + e.getMessage());

                }
                try {

                    if (inputBuffer != null && userVideoData != null) {
//                        if (show) {
//                            MyLog.logD("MediaCodecCallback::onInputBufferAvailable:inputBuffer.put:userVideoData.uidAndDevices:"
//                                    + userVideoData.uidAndDevices + " data:" + Arrays.toString(data));
//                        }
                        MyLog.logD(MyLog.DECODER_HANDLER_MODEL, " inputBuffer.put:");
                        inputBuffer.put(data, 0, data.length);
                    }
                } catch (Exception e) {
                    MyLog.logE(MyLog.DECODER_HANDLER_MODEL, "handleMessage: inputBuffer.put:" + e.getMessage());

                }
                try {
                    // Log.e(TAG, "handleMessage: queueInputBuffer:");
//                    if (show && userVideoData != null) {
//
//                        MyLog.logD("MediaCodecCallback::onInputBufferAvailable:queueInputBuffer:userVideoData.uidAndDevices:"
//                                + userVideoData.uidAndDevices);
//                    }
                    if (inputBuffer != null)
                        codec.queueInputBuffer(index, 0, inputBuffer.position(), 0, 0);
                    else
                        codec.queueInputBuffer(index, 0, 0, 0, 0);

                } catch (Exception e) {
                    MyLog.logE(MyLog.DECODER_HANDLER_MODEL, "handleMessage:  codec.queueInputBuffer:" + e.getMessage());
                }

            } else {
//                if (show && userVideoData != null) {
//                    if (data == null) {
//                        MyLog.logD("MediaCodecCallback::onInputBufferAvailable:data == null  :userVideoData.uidAndDevices:"
//                                + userVideoData.uidAndDevices);
//                    } else {
//                        MyLog.logD("MediaCodecCallback::onInputBufferAvailable: data.length==0 data:" + data + " data.toString:" + Arrays.toString(data) + ":userVideoData.uidAndDevices:"
//                                + userVideoData.uidAndDevices);
//                    }
//                }
                ByteBuffer inputBuffer = null;
                try {
                    inputBuffer = codec.getInputBuffer(index);
                    if (inputBuffer == null) {
                        MyLog.logE(MyLog.DECODER_HANDLER_MODEL, "handleMessage: inputBuffer == null:");
                    }
                } catch (Exception e) {
                    MyLog.logE(MyLog.DECODER_HANDLER_MODEL, "handleMessage: codec.getInputBuffer:" + e.getMessage());
                }
                byte[] emptyData = new byte[1024];
                try {
                    if (inputBuffer != null)
                        inputBuffer.put(emptyData, 0, emptyData.length);
                    codec.queueInputBuffer(index, 0, 0/*inputBuffer.position()*/, 0, 0);
                } catch (MediaCodec.CryptoException e) {
                    MyLog.logE(MyLog.DECODER_HANDLER_MODEL, "emptyData CryptoException: " + e.getMessage());
                    e.printStackTrace();
                } catch (Exception e) {
                    MyLog.logE(MyLog.DECODER_HANDLER_MODEL, "emptyData Exception: " + e.getMessage());
                }
            }

        }

        @Override
        public void onOutputBufferAvailable(@NotNull MediaCodec codec, int index, @NotNull MediaCodec.BufferInfo info) {
            MyLog.logD(MyLog.DECODER_HANDLER_MODEL, "onOutputBufferAvailable " + " " + getName());
            if (mediaCodecHandlerThread == null) {
                return;
            }
            Message msg = new Message();
            msg.what = CodecMsg.DEAL_AVAILABLE_OUTPUT;
            msg.arg1 = index;
            msg.arg2 = info.size;
            msg.obj = codec;
            if (myHandler != null) {
                myHandler.sendMessage(msg);
            }
            if (info.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
                MyLog.logE(MyLog.DECODER_HANDLER_MODEL, " info=" + info.flags);
            }
        }

        @Override
        public void onError(@NotNull MediaCodec codec, @NotNull MediaCodec.CodecException e) {
            MyLog.logE(MyLog.DECODER_HANDLER_MODEL, "onError:handleMessage " + e + " " + getName());
        }

        @Override
        public void onOutputFormatChanged(@NotNull MediaCodec codec, @NotNull MediaFormat format) {
            MyLog.logE(MyLog.DECODER_HANDLER_MODEL, "onOutputFormatChanged:handleMessage " + format + " " + getName());
        }
    }

    private static class MyHandler extends Handler {

        MyHandler(Looper looper) {
            super(looper);
        }

        private static final byte[] mLock = new byte[0];

        @Override
        public void handleMessage(Message msg) {
            synchronized (mLock) {
                if (msg.what == CodecMsg.DEAL_AVAILABLE_OUTPUT) {
                    MediaCodec codec = (MediaCodec) msg.obj;
                    int index = msg.arg1;
                    int OutputSize = msg.arg2;

                    try {
                        if (OutputSize > 0) {
                            codec.releaseOutputBuffer(index, true);
                        } else {
                            codec.releaseOutputBuffer(index, false);
                        }
                    } catch (Exception e) {
                        MyLog.logE(MyLog.DECODER_HANDLER_MODEL, "handleMessage:  codec.releaseOutputBuffer:" + e.getMessage());
                    }
                }
            }
        }
    }

    private static final class CodecMsg {
        static final int DEAL_AVAILABLE_INPUT = 5003;
        static final int DEAL_AVAILABLE_OUTPUT = 5004;
        static final int DEAL_ON_ERROR = 5;
        static final int DEAL_STATISTICS = 6;
    }

    @Override
    public String toString() {
        return super.toString() + "{" +
                "name='" + name + '\'' +
                ", mediaCodecStart=" + mediaCodecStart +
                ", mediaCodec=" + mediaCodec +
                ", mediaFormat=" + mediaFormat +
                ", mSurfaceTexture=" + mSurfaceTexture +
                '}';
    }

    public static class UserVideoData {
        public String uidAndDevices;
        public byte[] videoData;

        public UserVideoData(String uidAndDevices, byte[] videoData) {
            this.uidAndDevices = uidAndDevices;
            this.videoData = videoData;
        }

        @Override
        public String toString() {
            return "UserVideoData{" +
                    "uidAndDevices='" + uidAndDevices + '\'' +
                    ", videoData=" + videoData +
                    '}';
        }
    }
}
